package com.zwps.biz.domain.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zwps.biz.api.enums.UserIdentityEnum;
import com.zwps.biz.api.model.dto.auth.PasswordLoginAuthDTO;
import com.zwps.biz.api.model.dto.user.AddUserDTO;
import com.zwps.biz.api.model.dto.user.SelectUserDTO;
import com.zwps.biz.api.model.dto.user.UpdateUserDTO;
import com.zwps.biz.api.model.vo.auth.AuthInfoVO;
import com.zwps.biz.api.model.vo.permission.page.PagePermissionTreeVO;
import com.zwps.biz.api.model.vo.role.RoleVO;
import com.zwps.biz.api.model.vo.user.UserVO;
import com.zwps.biz.dal.db.dao.*;
import com.zwps.biz.dal.db.object.*;
import com.zwps.biz.dal.redis.cache.AuthCache;
import com.zwps.biz.domain.config.SystemAuthConfig;
import com.zwps.biz.domain.converter.DeptConverter;
import com.zwps.biz.domain.converter.UserConverter;
import com.zwps.biz.domain.enums.UserTypeEnum;
import com.zwps.biz.domain.service.PagePermissionService;
import com.zwps.biz.domain.service.UserService;
import com.zwps.biz.domain.tool.JwtTool;
import com.zwps.biz.domain.tool.TreeCodeTool;
import com.zwps.common.api.model.vo.LoginUserDeptVO;
import com.zwps.common.api.model.vo.LoginUserVO;
import com.zwps.common.api.model.vo.PageInfoVO;
import com.zwps.common.core.context.LoginInfoContext;
import com.zwps.common.core.enums.RegexEnum;
import com.zwps.common.core.exception.UserActionException;
import com.zwps.common.core.service.CommonService;
import com.zwps.common.tool.RegexTool;
import com.zwps.common.tool.crypto.Md5Tool;
import com.zwps.common.tool.crypto.Sm2Tool;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Service
@AllArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService, CommonService {
    private SysUserDAO sysUserDAO;

    private DeptDAO deptDAO;

    private SystemAuthConfig authConfig;

    private UserDAO userDAO;

    private UserRoleDAO userRoleDAO;

    private RoleDAO roleDAO;

    private PagePermissionService pagePermissionService;

    private AuthCache authCache;

    private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addUser(AddUserDTO dto) {
        LoginUserDeptVO loginDeptInfo = LoginInfoContext.getLoginInfo().getDept();
        if (StrUtil.isNotEmpty(dto.getDeptId())) {
            DeptDO dept = deptDAO.selectById(dto.getDeptId());
            if (dept == null) {
                throw new UserActionException("部门不存在或已被删除");
            }
            if (!dept.getTreeCode().startsWith(loginDeptInfo.getTreeCode())) {
                throw new UserActionException("无权限绑定其他部门");
            }
        } else {
            dto.setDeptId(loginDeptInfo.getId());
        }

        if (StrUtil.isNotEmpty(dto.getPhone())) {
            checkValueUnique(userDAO, UserDO.class, UserDO::getPhone, dto.getPhone(), "手机号码已被使用");
            if (!StrUtil.isNotEmpty(dto.getUsername())) {
                dto.setUsername(dto.getPhone());
            }
        }

        if (StrUtil.isNotEmpty(dto.getEmail())) {
            checkValueUnique(userDAO, UserDO.class, UserDO::getEmail, dto.getEmail(), "邮箱已被使用");
        }

        UserDO user = UserConverter.INSTANCE.toUserDO(dto);
        user.setPassword(passwordEncoder.encode(authConfig.getPasswordDefault()));
        if (userDAO.insert(user) < 1) {
            throw new UserActionException("新增用户失败");
        }
        if (CollectionUtil.isNotEmpty(dto.getRoleIds())) {
            addUserRole(dto.getRoleIds(), user.getId());
        }

    }

    public void addUserRole(Set<String> roleIds, String userId) {
        LambdaQueryWrapper<RoleDO> roleQuery = Wrappers.lambdaQuery(RoleDO.class).in(RoleDO::getId, roleIds);
        if (roleDAO.selectCount(roleQuery) != roleIds.size()) {
            throw new UserActionException("查询的角色数量与系统存在角色数量不匹配，请刷新角色选项重试");
        }
        List<UserRoleDO> userRoleList = new ArrayList<UserRoleDO>();
        for (String roleId : roleIds) {
            userRoleList.add(new UserRoleDO(userId, roleId));
        }
        if (userRoleDAO.insertBatchSomeColumn(userRoleList) != roleIds.size()) {
            throw new UserActionException("新增用户角色关联失败");
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteUsers(String ids) {

        List<String> idList = Arrays.asList(ids.split(","));
        int result = userDAO.deleteBatchIds(idList);
        if (result != idList.size()) {
            long UserNum = userDAO.selectCount(Wrappers.lambdaQuery(UserDO.class).in(UserDO::getId, idList));
            if (UserNum > 0) {
                log.error("Delete Users id[{}] fail, select item {}, delete success {}", ids, idList.size(), result);
                throw new UserActionException("删除失败");
            }
        }
        userRoleDAO.delete(Wrappers.lambdaQuery(UserRoleDO.class).in(UserRoleDO::getUserId, idList));
    }

    /**
     * 更新用户密码检查
     *
     * @param selectdUser
     * @param oldPassword
     * @param newPassword
     * @param confirmPassword
     * @param update          void
     * @Title: updatePasswordCheck
     * @Description:
     * @author lyh
     * @date 2023年5月24日 下午3:28:35
     */
    private void updatePasswordCheck(UserDO selectdUser, String oldPassword, String newPassword, String confirmPassword,
                                     LambdaUpdateWrapper<UserDO> update) {
        if (StrUtil.isEmpty(newPassword)) {
            return;
        }
        if (StrUtil.isEmpty(oldPassword)) {
            throw new UserActionException("原密码不能为空");
        }
        if (StrUtil.isEmpty(oldPassword)) {
            throw new UserActionException("确认密码不能为空");
        }
        if (!confirmPassword.equals(newPassword)) {
            throw new UserActionException("新密码与确认密码不一致");
        }
        String oldRawPassword;
        String newRawPassword;
        try {

            newRawPassword = Sm2Tool.decrypt(authConfig.getPasswordPrivateKey(), newPassword);
            oldRawPassword = Sm2Tool.decrypt(authConfig.getPasswordPrivateKey(), oldPassword);
        } catch (Exception e) {
            log.error("AES decrypt old: [{}], new: [{}] error: ", oldPassword, newPassword, e);
            throw new UserActionException("密码需要按规则进行加密");
        }
        if (!RegexTool.checkPassword(newRawPassword, RegexEnum.PASSWORD.getCode())) {
            throw new UserActionException("新密码不符合要求，" + RegexEnum.PASSWORD.getName());
        }
        if (!passwordEncoder.matches(oldRawPassword, selectdUser.getPassword())) {
            throw new UserActionException("原密码错误");
        }
        update.set(UserDO::getPassword, passwordEncoder.encode(newRawPassword));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateUser(String id, UpdateUserDTO dto) {
        UserDO selectdUser = userDAO.selectById(id);
        if (selectdUser == null) {
            throw new UserActionException("修改的用户不存在");
        }
        LambdaUpdateWrapper<UserDO> update = Wrappers.lambdaUpdate(UserDO.class);
        updatePasswordCheck(selectdUser, dto.getOldPassword(), dto.getNewPassword(), dto.getConfirmPassword(), update);
        update.set(change(dto.getRealname(), selectdUser.getRealname()), UserDO::getRealname, dto.getRealname());
        update.set(change(dto.getSex(), selectdUser.getSex()), UserDO::getSex, dto.getSex());
        update.set(change(dto.getDuties(), selectdUser.getDuties()), UserDO::getDuties, dto.getDuties());

        if (change(dto.getPhone(), selectdUser.getPhone())) {
            if (StrUtil.isNotEmpty(dto.getPhone())) {
                checkValueUnique(userDAO, UserDO.class, UserDO::getPhone, dto.getPhone(), "手机号码已被使用");
            }
            update.set(UserDO::getPhone, dto.getPhone());
        }

        if (change(dto.getEmail(), selectdUser.getEmail())) {
            if (StrUtil.isNotEmpty(dto.getEmail())) {
                checkValueUnique(userDAO, UserDO.class, UserDO::getEmail, dto.getEmail(), "邮箱已被使用");
            }
            update.set(UserDO::getEmail, dto.getEmail());
        }

        update.set(change(dto.getStatus(), selectdUser.getStatus()), UserDO::getStatus, dto.getStatus());
        update.set(change(dto.getUserIdentity(), selectdUser.getUserIdentity()), UserDO::getUserIdentity,
                dto.getUserIdentity());

        if (change(dto.getDeptId(), selectdUser.getDeptId())) {
            DeptDO dept = deptDAO.selectById(dto.getDeptId());
            if (dept == null) {
                throw new UserActionException("部门不存在或已被删除");
            }
            LoginUserDeptVO loginDeptInfo = LoginInfoContext.getLoginInfo().getDept();
            if (!dept.getTreeCode().startsWith(loginDeptInfo.getTreeCode())) {
                throw new UserActionException("无权限绑定其他部门");
            }
            update.set(UserDO::getDeptId, dto.getDeptId());
        }
        if (CollectionUtil.isNotEmpty(dto.getRoleIds())) {
            LambdaQueryWrapper<UserRoleDO> delete = Wrappers.lambdaQuery(UserRoleDO.class);
            delete.eq(UserRoleDO::getUserId, id);
            userRoleDAO.delete(delete);
            try {
                addUserRole(dto.getRoleIds(), selectdUser.getId());
            } catch (DuplicateKeyException e) {
                throw new UserActionException("请勿关联的角色");
            }
        }

        if (StrUtil.isNotEmpty(update.getSqlSet())) {
            update.eq(UserDO::getId, id);
            if (userDAO.update(null, update) < 1) {
                throw new UserActionException("修改用户失败");
            }
        }
    }

    @Override
    public PageInfoVO<UserVO> selectUserPage(SelectUserDTO dto) {
        LoginUserDeptVO currentDept = LoginInfoContext.getLoginInfo().getDept();
//        LoginUserVO currentUser = LoginInfoContext.getLoginInfo().getUser();
//        dto.setCurrentUserId(currentUser.getId());
        if (currentDept == null) {
            throw new UserActionException("当前用户的部门不存在");
        }
        Set<String> deptIds = deptDAO.findChildrenDeptIds(currentDept.getTreeCode(), "");
        if (!deptIds.isEmpty()) {
            dto.setDeptIds(deptIds);
        }
        PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
        List<UserDO> userList = userDAO.selectUserPage(dto);

        PageInfoVO<UserVO> pageInfo;
        ArrayList<UserVO> data = new ArrayList<UserVO>();
        try (Page<UserDO> page = (Page<UserDO>) userList) {
            pageInfo = new PageInfoVO<UserVO>(page.getPageNum(), page.getPageSize(), page.getPages(), page.getTotal(),
                    data);
        }
        if (userList.isEmpty()) {
            return pageInfo;
        }
        HashMap<String, String> deptMap = new HashMap<String, String>();
        for (UserDO user : userList) {
            UserVO userVO = UserConverter.INSTANCE.toUserVO(user);
            String deptName = deptMap.get(userVO.getDeptId());
            if (deptName == null) {
                DeptDO dept = deptDAO.selectById(userVO.getDeptId());
                if (dept == null) {
                    throw new UserActionException(String.format("用户【%s】的部门不存在", userVO.getUsername()));
                }
                deptMap.put(userVO.getDeptId(), deptName = dept.getDeptName());

            }
            userVO.setCreateTime(user.getCreateTime());
            userVO.setDeptName(deptName);

            List<String> roleNameList = new ArrayList<>();
            List<String> roleIdList = new ArrayList<>();
            List<RoleVO> roleVOList = userRoleDAO.selectAllRoleByUserId(user.getId());
            if (!roleVOList.isEmpty()) {
                for (RoleVO roleVO : roleVOList) {
                    roleNameList.add(roleVO.getRoleName());
                    roleIdList.add(roleVO.getId());
                }
                userVO.setRoleName(roleNameList);
                userVO.setRoleIds(roleIdList);
            }

            data.add(userVO);
        }
        return pageInfo;
    }

    @Override
    public Set<String> getDataSopeUserIds(String userId) {
        UserDO selectdUser = userDAO.selectById(userId);
        if (selectdUser == null && sysUserDAO.selectById(userId) == null) {
            throw new UserActionException("用户不存在");
        }
        // 管理员
        if (UserIdentityEnum.ADMIN.getCode().equals(selectdUser != null ? selectdUser.getUserIdentity() : null)
                && StrUtil.isNotEmpty(selectdUser.getDeptId())) {
            DeptDO dept = deptDAO.selectById(selectdUser.getDeptId());
            if (dept == null) {
                throw new UserActionException("部门不存在或已被删除");
            }
            return userDAO.selectUserIds(dept.getTreeCode());
        }
        HashSet<String> set = new HashSet<String>();
        set.add(userId);
        return set;
    }

    @Override
    public void resetPassWord(String id) {
        UserDO selectdUser = userDAO.selectById(id);
        if (selectdUser == null) {
            throw new UserActionException("用户不存在");
        }
        LambdaUpdateWrapper<UserDO> update = Wrappers.lambdaUpdate(UserDO.class);
        update.set(UserDO::getPassword, passwordEncoder.encode(authConfig.getPasswordDefault()));
        if (StrUtil.isNotEmpty(update.getSqlSet())) {
            update.eq(UserDO::getId, id);
            if (userDAO.update(null, update) < 1) {
                throw new UserActionException("修改用户失败");
            }
        }
    }

    @Override
    public void setUpStatus(String id) {
        LoginUserVO currentUser = LoginInfoContext.getLoginInfo().getUser();
        if (id.equals(currentUser.getId())) {
            throw new UserActionException("无法修改自己的账号状态，请联系管理员操作！");
        }
        UserDO userDO = userDAO.selectById(id);
        if (userDO == null) {
            throw new UserActionException("该用户不存在");
        }
        // 状态(0：正常|1：冻结)
        if (userDO.getStatus() == 1) {
            userDO.setStatus(0);
        } else if (userDO.getStatus() == 0) {
            userDO.setStatus(1);
        }
        LambdaUpdateWrapper<UserDO> update = Wrappers.lambdaUpdate(UserDO.class);
        update.set(UserDO::getStatus, userDO.getStatus());
        if (StrUtil.isNotEmpty(update.getSqlSet())) {
            update.eq(UserDO::getId, id);
            if (userDAO.update(null, update) < 1) {
                throw new UserActionException("修改状态失败");
            }
        }
    }

    @Override
    public List<UserVO> selectAllUser(String filtration) {
        LambdaQueryWrapper<UserDO> wrapper = new QueryWrapper<UserDO>().lambda();
        wrapper.eq(UserDO::getStatus, 0);
        if (StringUtils.isBlank(filtration) || !"1".equals(filtration)) {
            LoginUserVO user = LoginInfoContext.getLoginInfo().getUser();
            wrapper.eq(UserDO::getDeptId, user.getDeptId());
        }
        List<UserDO> userDOList = userDAO.selectList(wrapper);
        ArrayList<UserVO> list = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(userDOList)) {
            for (UserDO userDO : userDOList) {
                UserVO userVO = UserConverter.INSTANCE.toUserVO(userDO);
                list.add(userVO);
            }
        }
        return list;
    }

    @Override
    public boolean exist(String id) {
        return userDAO.selectById(id) != null;
    }

    @Override
    public List<PagePermissionTreeVO> byLoginedUserFindPagePermissionTree() {
        LoginUserVO currentUser = LoginInfoContext.getLoginInfo().getUser();
        if (UserTypeEnum.SUPER_ADMIN.getCode().equals(currentUser.getUserType())) {
            return pagePermissionService.findTree(null);
        }
        return byUserIdFindPagePermissionTree(currentUser.getId());
    }

    @Override
    public List<PagePermissionTreeVO> byUserIdFindPagePermissionTree(String userId) {
        List<PagePermissionDO> deptList = userRoleDAO.selectPagePermissionsByUserId(userId);
        if (deptList.isEmpty()) {
            return CollUtil.newArrayList();
        }
        Set<String> treeCodes = TreeCodeTool.splitTreeCodesAssembly(deptList, (a) -> a.getTreeCode());
        return pagePermissionService.findTreeByTreeCodes(treeCodes);
    }

    @Override
    public UserVO getUserInfoById(String userId) {
        UserDO userDo = userDAO.selectById(userId);
        UserVO userVO = new UserVO();
        if (userDo != null) {
            BeanUtils.copyProperties(userDo, userVO);
        }
        return userVO;
    }

    @Override
    public List<String> getUserInfoByLikeUserName(String userName) {
        return userDAO.getUserInfoByLikeUserName(userName);
    }

    @Override
    public AuthInfoVO getCurrentUserInfor() {
        LoginUserVO currentUser = LoginInfoContext.getLoginInfo().getUser();
        UserDO user = userDAO.selectById(currentUser.getId());
        AuthInfoVO loginInfo = new AuthInfoVO();
        loginInfo.setUser(UserConverter.INSTANCE.toUserLoginInfoVO(user));
        String tokenId = JwtTool.generateId(user.getId());
        String token = JwtTool.generateToken(user.getId(), tokenId);
        loginInfo.setToken(token);
        authCache.setToken(tokenId, token, authConfig.getTokenExpireTime());
        loginInfo.setDept(DeptConverter.INSTANCE.toDeptInfoVO(deptDAO.selectById(user.getDeptId())));
        if (UserTypeEnum.SUPER_ADMIN.getCode().equals(user.getUserType())) {
            loginInfo.setPagePermissionTree(pagePermissionService.findTree(null));
        } else {
            loginInfo.setPagePermissionTree(this.byUserIdFindPagePermissionTree(user.getId()));
        }
        return loginInfo;
    }

    @Override
    public String loginSecretKey(PasswordLoginAuthDTO loginDTO) {
        String password;
        try {
            password = Sm2Tool.decrypt(authConfig.getPasswordPrivateKey(), loginDTO.getPassword());
            System.out.println(password);
        } catch (Exception e) {
            log.error("SM2 decrypt password error for username: [{}]", loginDTO.getUsername(), e);
            throw new UserActionException("密码需要按规则进行加密");
        }
        // Step 1: 根据用户名从两个表中查询用户信息
        UserDO userDO = userDAO.selectOne(Wrappers.lambdaQuery(UserDO.class)
                .eq(UserDO::getUsername, loginDTO.getUsername()));
        SysUserDO sysUserDO = sysUserDAO.selectOne(Wrappers.lambdaQuery(SysUserDO.class)
                .eq(SysUserDO::getUsercode, loginDTO.getUsername()));

        // Step 2: 检查用户是否存在
        if (Objects.isNull(userDO) && Objects.isNull(sysUserDO)) {
            throw new UserActionException("用户不存在");
        }
        boolean isPasswordMatched = false;
        // Step 4: 尝试使用 BCrypt 验证密码
        if (userDO != null) {
            isPasswordMatched = passwordEncoder.matches(password, userDO.getPassword());
        }
        // Step 5: 如果 BCrypt 验证失败，并且用户在 sysUser 表中，使用 MD5 验证
        boolean isMD5Matched = false;
        if (!isPasswordMatched && sysUserDO != null) {
            String md5HashedPassword = Md5Tool.encode(loginDTO.getUsername()+password);
            System.out.println("md5:"+md5HashedPassword);
            isMD5Matched = md5HashedPassword.equals(sysUserDO.getPassword());
            System.out.println("库中:"+sysUserDO.getPassword());
        }
        // Step 6: 确保至少有一种方式匹配成功
        if (!isPasswordMatched && !isMD5Matched) {
            throw new UserActionException("密码错误");
        }
        // Step 7: 准备返回用户信息
        if (sysUserDO != null && isMD5Matched) {
            userDO = new UserDO();
            userDO.setId(String.valueOf(sysUserDO.getUserId()));
            userDO.setUsername(sysUserDO.getUsercode());
            userDO.setRealname(sysUserDO.getUsername());
            userDO.setPassword(sysUserDO.getPassword());
            userDO.setStatus(0);
        }
        // Step 8: 生成密钥并存储到缓存
        String key = DigestUtil.md5Hex(DigestUtil.md5Hex(userDO.getId()));
        authCache.setToken(key, userDO.getId(), authConfig.getTokenExpireTime());
        return key;
    }
}